﻿using System;
using System.Collections.Generic;
using System.Text;
using Co9320.NamaAlert.Engine.Feeder.Util;
using Co9320.NamaAlert.Model;
using System.Xml;
using System.Diagnostics;
using System.Net;


namespace Co9320.NamaAlert.Engine.Feeder
{
	internal enum CurrentStatus : byte
	{
		OnAir,
		CommingSoon,
		Closed
	}


	internal class LiveProgramSet
	{
		public LiveProgramSet(LiveProgram Program, CurrentStatus Status)
		{
			this.Program = Program;
			this.Status = Status;
		}

		public LiveProgram Program
		{
			get;
			private set;
		}

		public CurrentStatus Status
		{
			get;
			private set;
		}
	}


	internal static class ReserveDataFeeder
	{


		private class UriBuilder
		{
			private const string BaseUri = "http://live.nicovideo.jp/api/timeline.php?start={0}&end={1}&left={2}&right={3}";
			private const long RoundParameter = 18000000000L;
			private const int Interval = 1800;
			private const int BatchReableCount = 5;
			private readonly TimeSpan Span = new TimeSpan(0, 30, 0);

			private long UnixStart_;
			private int ReadStart_;

			public UriBuilder(DateTime Start)
			{
				UnixStart_ = Start.Round().ToUnixTime();
				ReadStart_ = 0;
			}

			public DateTime Start
			{
				get
				{
					return UnixStart_.ToDateTime();
				}
				set
				{
					UnixStart_ = value.Round().ToUnixTime();
					ReadStart_ = 0;
				}
			}

			public DateTime End
			{
				get
				{
					return (UnixStart_ + Interval).ToDateTime();
				}
			}

			public void MoveNextRow()
			{
				this.Start += Span;
			}

			public void NextColumnPosition()
			{
				if (ReadStart_ == 0)
				{
					ReadStart_ = 1;
				}
				else
				{
					ReadStart_ += (BatchReableCount + 1);
				}
			}

			public string GetUri()
			{
				if (ReadStart_ == 0)
				{
					return null;
				}
				else
				{
					return string.Format(BaseUri, UnixStart_, (UnixStart_ + Interval), ReadStart_, ReadStart_ + BatchReableCount);
				}
			}

			public void ResetColumnPosition()
			{
				ReadStart_ = 0;
			}


		}

		private static class Parser
		{
			private const string XPath = "tsv";
			private const char RowSplitter = '\n';
			private const char ColumnSplitter = '\t';

			public static List<LiveProgramSet> Parse(XmlDocument Source,DateTime GetTime)
			{
				Debug.Assert(Source != null);
				List<LiveProgramSet> Ret = new List<LiveProgramSet>();

				string[] InnerTxt = Source.SelectSingleNode(XPath).InnerText.Split(RowSplitter);

				for (int i = 0; i < InnerTxt.Length; i++)
				{
					var result = BuildProgram(InnerTxt[i], GetTime);

					if (result != null)
					{
						Ret.Add(result);
					}
				}

				return Ret;

			}

			private static LiveProgramSet BuildProgram(string Source,DateTime GetTime)
			{
				Debug.Assert(Source != null);

				if (Source.Trim().Length == 0)
				{
					return null;
				}

				string[] elems = Source.Split(ColumnSplitter);

				if (elems[9].Trim().Length==0)
				{
					return null;
				}

				// elems[18] tosu 凸待ち
                // elems[19] お知らせ登録人数
                // 
				LiveProgram Ret = new LiveProgram();
				Ret.IsReserved = true;

				
				//Set GUID
				Ret.Id = int.Parse(elems[1].Trim('l', 'v'));

				//Set GetTime.
				Ret.CrawlingDateTime = GetTime;

				//Set StartTime;
				Ret.StartDateTime = long.Parse(elems[3]).ToDateTime();

				//Set Title
				Ret.Title = elems[8];

				//Set StationID
				Ret.Station.Id = elems[10];
				Ret.Station.Name = elems[11];

				//SetCaster
				Ret.CasterName = null;

				//Set desc.
				Ret.ShortContent = elems[12];

				//SetTag
                string tag = "";
                switch (elems[16])
                {
                    case "common":
                        tag = "一般(その他)";
                        break;
                    case "politics": // ?
                        tag = "政治";
                        break;
                    case "animal":
                        tag = "動物";
                        break;
                    case "cooking":
                        tag = "料理";
                        break;
                    case "play":
                        tag = "演奏してみた";
                        break;
                    case "sing":
                        tag = "歌ってみた";
                        break;
                    case "lecture":
                        tag = "講座";
                        break;
                    case "live":
                        tag = "ゲーム実況";
                        break;
                    case "request":
                        tag = "動画紹介・リクエスト";
                        break;
                    case "R18":
                        tag = "R18";
                        break;
                    default:
                        tag = elems[16];
                        break;
                }
				Ret.OfficialTag = tag;

				//SetFace
				if (elems[17] == "face")
				{
					Ret.IsFace = true;
				}
				else
				{
					Ret.IsFace = false;
				}

				if (elems[18] == "totu")
				{
					Ret.IsTotsu = true;
				}
				else
				{
					Ret.IsTotsu = false;
				}


				if (elems[7] == "closed")
				{
					return new LiveProgramSet(Ret, CurrentStatus.Closed);
				}
				else if (elems[7] == "onair")
				{
					return new LiveProgramSet(Ret, CurrentStatus.OnAir);
				}
				else if (elems[7] == "comingsoon")
				{
					return new LiveProgramSet(Ret, CurrentStatus.CommingSoon);
				}
				else
				{
					Debug.Assert(false);
					return null;
				}


			}
		}

		private static void GetDocument(string Uri, out DateTime GetTime, out XmlDocument Document)
		{
			Debug.Assert(Uri != null);

			WebRequest req = WebRequest.Create(Uri);

			GetTime = new DateTime();
			Document = null;


			for (int i = 0; ; i++)
			{
				try
				{
					using (WebResponse response = req.GetResponse())
					{
						GetTime = GetHeaderTime(response);

						using (System.IO.Stream str = response.GetResponseStream())
						{
							XmlDocument doc = new XmlDocument();
							doc.Load(str);

							Document = doc;
						}
					}

					break;
				}
				catch(Exception ex)
				{
					DebugHelper.LogOutputter.WriteElement(Co9320.NamaAlert.Engine.DebugHelper.LogMode.Warning, ex);
					if (i < Define.ExternalRetryLimit)
					{
						System.Threading.Thread.Sleep(Define.InternalRetryDelay);
						continue;
					}
					else
					{
						throw;
					}
				}
			}
		}

		public static IList<LiveProgramSet> GetCurrentReserve(out DateTime FrameStart)
		{
			return GetReserve(false,out FrameStart);
		}

		public static IList<LiveProgramSet> GetNextReserve(out DateTime FrameStart)
		{
			return GetReserve(true,out FrameStart);
		}

		private static IList<LiveProgramSet> GetReserve(bool IsNext,out DateTime FrameStart)
		{
			UriBuilder bld = new UriBuilder(DateTime.Now);
			List<LiveProgramSet> Sets = new List<LiveProgramSet>();

			if (IsNext)
			{
				bld.MoveNextRow();
			}
			FrameStart = bld.Start;


			for (; ; )
			{
				DateTime GetTime;
				XmlDocument Document;
				List<LiveProgramSet> lst;

				bld.NextColumnPosition();
				GetDocument(bld.GetUri(), out GetTime, out Document);
				lst = Parser.Parse(Document, GetTime);

				if (lst.Count == 0)
				{
					break;
				}
				else
				{
					for (int i = 0; i < lst.Count; i++)
					{
						Sets.Add(lst[i]);
					}
				}
			}

			return Sets;
		}

		private static DateTime GetHeaderTime(WebResponse response)
		{
			DateTime GetTime = new DateTime();

			try
			{
				GetTime = DateTime.Parse(response.Headers.Get("Date"));
			}
			catch (FormatException)
			{
				GetTime = DateTime.Now;
			}
			catch (ArgumentNullException)
			{
				GetTime = DateTime.Now;
			}

			return GetTime;
		}

	}
}
